package edu.hm.devcamp.util;

import java.util.List;

import android.util.FloatMath;
import android.util.Log;

import com.google.android.maps.GeoPoint;

import edu.hm.devcamp.presentation.LocationListenerImpl;
import edu.hm.devcamp.presentation.activities.PoiMap;
import edu.hm.devcamp.providers.Poi;

/**
 * this class provides the basic features used for geo-based augmented reality
 */
public class GeoUtils {

    private static final String TAG = "GeoUtils";

    /**
     * Calculates the distance of two geo points in meter
     * 
     * @param geo1 the first {@link GeoPoint}
     * @param geo2 the second {@link GeoPoint}
     * @return the distance of the two points
     */
    public static float calculateDistanceBetweenGeopoints(final GeoPoint geo1, final GeoPoint geo2) {

        float lat1 = (float)(geo1.getLatitudeE6() / 1e6);
        float lon1 = (float)(geo1.getLongitudeE6() / 1e6);

        float lat2 = (float)(geo2.getLatitudeE6() / 1e6);
        float lon2 = (float)(geo2.getLongitudeE6() / 1e6);

        float pk = (float)(180 / Math.PI);

        float a1 = lat1 / pk;
        float a2 = lon1 / pk;
        float b1 = lat2 / pk;
        float b2 = lon2 / pk;

        float t1 = FloatMath.cos(a1) * FloatMath.cos(a2) * FloatMath.cos(b1) * FloatMath.cos(b2);
        float t2 = FloatMath.cos(a1) * FloatMath.sin(a2) * FloatMath.cos(b1) * FloatMath.sin(b2);
        float t3 = FloatMath.sin(a1) * FloatMath.sin(b1);
        float tt = (float)(Math.acos(t1 + t2 + t3));

        return 6366000 * tt;
    }

    /**
     * calculates the angle of one {@link GeoPoint} to another in a right
     * turning 360 degree circle
     * 
     * @param position the current position of the device
     * @param point the other {@link GeoPoint}
     * @return the angle the two {@link GeoPoint}s are having towards each other
     */
    public static float calculateAngleOfGeopoints(final GeoPoint position, final GeoPoint point) {
        // pythagoras, so it's actually not perfectly correct, but should do it
        // for a smaller area like a city
        float a = (float)(position.getLongitudeE6() / 1e6 - point.getLongitudeE6() / 1e6);
        float b = (float)(position.getLatitudeE6() / 1e6 - point.getLatitudeE6() / 1e6);
        float c = (float)(Math.sqrt((double)(a * a + b * b)));

        float cosine = b / c;

        // calculate the arcsin. result is in radian, so we make degrees out of
        // it.
        float alpha = (float)(Math.acos(cosine) * 180 / Math.PI);
        if (a > 0) {
            alpha = 180 + alpha;
        }

        return alpha;
    }

    /**
     * determines whether a point is in the view area of a camera or not
     * 
     * @param cameraAngle the view angle of the camera
     * @param deviceAngle the position the device is heading relative to the
     *            magnetic north pole
     * @param anglePointToPosition the angle of the {@link GeoPoint} to the
     *            current position of the device. use {@link
     *            calculateAngleOfGeopoints()} to get this value.
     * @return whether the point is in the view of the camera or not
     */
    public static boolean isInViewArea(final float cameraAngle, final float deviceAngle,
            final float anglePointToPosition) {

        // this has to be done often, so no division here

        float viewableAreaStart = deviceAngle - cameraAngle * 0.5f;
        if (viewableAreaStart < 0) {
            viewableAreaStart = 360 - viewableAreaStart;
        }

        float viewableAreaEnd = deviceAngle + cameraAngle * 0.5f;
        if (viewableAreaEnd > 360) {
            viewableAreaEnd = viewableAreaEnd - 360;
        }

        return anglePointToPosition > viewableAreaStart && anglePointToPosition < viewableAreaEnd;

    }
    
    /**
     * same as {@link inViewArea(final float cameraAngle, final float deviceAngle, final float anglePointToPosition)}, but no angle
     * to position, but a {@link Poi} is needed. The Pois anglePercentega will also be updated.
     * @param cameraAngle the angle of the camera
     * @param deviceAngle the angle of the device relative to the magnetic north pole
     * @param poi the {@link Poi}
     * @return whether the Poi is in the view area of the camera or not. 
     */
    public static boolean isInViewAreaUpdatePoi(final float cameraAngle, final float deviceAngle, final Poi poi){
        float enhancedCameraAngle = cameraAngle * 1.1f; 
        float angle = GeoUtils.calculateAngleOfGeopoints(
                LocationListenerImpl.currentLocation, new GeoPoint(
                        (int)(poi.latitude * 1e6), (int)(poi.longitude * 1e6)));
        
        float viewableAreaStart = deviceAngle - enhancedCameraAngle * 0.5f;
        if (viewableAreaStart < 0) {
            viewableAreaStart = 360 - viewableAreaStart;
        }

        float viewableAreaEnd = deviceAngle + enhancedCameraAngle * 0.5f;
        if (viewableAreaEnd > 360) {
            viewableAreaEnd = viewableAreaEnd - 360;
        }
        
        boolean isInViewArea = angle > viewableAreaStart && angle < viewableAreaEnd;
        
        if(isInViewArea) {
            if(angle < viewableAreaStart){
                angle += 360;
            }            
            
            float angleDif = angle - viewableAreaStart;            
            float percentage = angleDif / cameraAngle;
            
            poi.anglePercentage = percentage;            
        }

        return isInViewArea;
    }

    /**
     * updates the distance of a list of {@link Poi}s to a given
     * {@link GeoPoint}.
     * 
     * @param pois the list of {@link Poi}s
     * @param position the current position of the device
     * @return whether the update was successful or not
     */
    public static boolean updatePoiListDistance(final List<Poi> pois, final GeoPoint position) {
        for (Poi poi : pois) {
            poi.distance = calculateDistanceBetweenGeopoints(new GeoPoint(
                    (int)((poi.latitude * 1E6)), (int)((poi.longitude * 1E6))), position);
        }

        return true;
    }
}
